package com.example.emqx.redis;

import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;

import java.util.UUID;
import java.util.concurrent.TimeUnit;

/**
 * 1. 使用 SETNX 加锁
 * Redis 提供了 SETNX（SET if Not Exists）命令来实现分布式锁。结合过期时间和唯一标识符，避免死锁。
 * <p>
 * 实现步骤
 * 使用 SETNX 来尝试获取锁。
 * 设置锁的过期时间，以防死锁。
 * 解锁时，通过验证锁的唯一标识符，确保只释放自己持有的锁。
 */
@Service
@Slf4j
public class RedisLockService {
    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    /**
     * 尝试在指定的等待时间 waitTime 内，间隔一定的时间（比如 100ms）去尝试获取锁。
     * 如果在等待时间内获取到了锁，设置锁的自动释放时间 leaseTime。
     * 如果超过等待时间仍未获取到锁，则返回失败。
     *
     * @param lockKey   尝试获取锁：使用 SETNX 命令来判断锁是否已经存在。
     * @param waitTime  等待获取锁：在 waitTime 之内，不断尝试获取锁。
     * @param leaseTime 设置锁的过期时间：如果成功获取到锁，使用 EXPIRE 命令设置锁的持有时间（leaseTime），避免死锁。
     * @param lockValue 唯一标识符
     * @return
     */
    public boolean tryLock(String lockKey, String lockValue,int waitTime, int leaseTime) {
        log.info("tryLock lockKey:{}, lockValue:{},Thread.currentThread().getName():{} ", lockKey,lockValue,Thread.currentThread().getName());
        int attempts = 0;

        // 等待的结束时间
        long endTime  = System.currentTimeMillis() + waitTime;
        try {
            while (System.currentTimeMillis() < endTime ) {
                /**
                 * setIfAbsent(String key, String value, long timeout, TimeUnit unit)：
                 * 这是 ValueOperations 中的一个方法，用于将 value 设置到 Redis 中指定的 key 上。
                 * 只有在 key 不存在的情况下才会成功设置。如果 key 已经存在，则不会进行设置操作。
                 */
                attempts++;
                Boolean success = stringRedisTemplate.opsForValue().setIfAbsent(lockKey, lockValue, leaseTime, TimeUnit.SECONDS);
                log.info("tryLock success:{},Thread.currentThread().getName():{} ,attempts:{}", success,Thread.currentThread().getName(),attempts);
                if (Boolean.TRUE.equals(success)) {
                    // 成功获取锁
                    return true;
                }
                // 等待 100 毫秒后再次尝试
                Thread.sleep(100);
            }
        }catch (InterruptedException e){
            log.info("tryLock InterruptedException Thread.currentThread().getName():{} ,attempts:{}",  Thread.currentThread().getName(),attempts);
            e.printStackTrace();
            Thread.currentThread().interrupt(); // 恢复线程中断状态
        }
        // 超过等待时间，获取锁失败
        log.info("tryLock 超过等待时间，获取锁失败 Thread.currentThread().getName():{}",  Thread.currentThread().getName());

        return  false;

    }

    // 释放锁
    public boolean unlock(String lockKey, String lockValue) {
        String value = stringRedisTemplate.opsForValue().get(lockKey);
        log.info("unlock :value:{}，lockValue:{},Thread.currentThread().getName():{}", value,lockValue,Thread.currentThread().getName());
        if (lockValue.equals(value)) {
            // 只删除属于自己的锁
            log.info("只删除属于自己的锁");
             stringRedisTemplate.delete(lockKey);
             return true;
        }
        return false;

    }
    // 使用示例
    public void doWithLock(String lockKey) {
        String lockValue = UUID.randomUUID().toString();
        try {
            if (tryLock(lockKey, lockValue,5, 10)) { // 等待 5 秒，锁持有时间为 10 秒
                // 业务逻辑
                System.out.println("成功获取锁，执行业务操作...");
            } else {
                System.out.println("获取锁失败");
            }
        } finally {
            unlock(lockKey, lockValue); // 释放锁
        }
    }
}
